﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

namespace CounterStrikeSourceAimbot
{
    public class MemoryEngine
    {
        public Process Hl2ExeProcess { get; set; }

        public WorldData WorldData;

        public string MainPlayerName;

        public MemoryEngine(Process hl2ExeProcess)
        {
            Hl2ExeProcess = hl2ExeProcess;
        }



        public int PlayerListStartAddress { get; set; }

        public bool ArrayContainsPlayerName(byte[] mem,string playerName,out int localOffset)
        {
            var pn = new byte[30];

            var nmBytes = Encoding.UTF8.GetBytes(playerName);

            for (int i = 0; i < 30; i++)
            {
                if (i < nmBytes.Length)
                    pn[i] = nmBytes[i];
            }

            for (var i = 0; i < mem.Length - pn.Length; i++)
            {
                var flag = true;

                for (int j = 0; j < nmBytes.Length; j++)
                {
                    if (mem[i + j] != pn[j])
                    {
                        flag = false;
                        j = pn.Length;
                    }
                }

                if (flag)
                {
                    localOffset = i;
                    return true;
                }

            }

            localOffset = -1;
            return false;
        }

        public int FindPlayerListAddress(string localPlayerName)
        {
            int pc;

            return FindPlayerListAddress(localPlayerName, out pc);
        }

        public int FindPlayerListAddress(string localPlayerName, out int playerCount)
        {
            playerCount = 0;

            var prc = Hl2ExeProcess;

            var areas = GetMemoryInfo(prc.Handle);

            var rdr = new ProcessMemoryReaderLib.ProcessMemoryReader();

            rdr.ReadProcess = prc;

            rdr.OpenProcess();

            var bytesRead = 0;

            var playersLocalOffset = 0;

            var t = Guid.NewGuid().ToString() != "";

            var cnt = 0;


            foreach (var area in areas)
            {
                cnt++;

                var buf = rdr.ReadProcessMemory((IntPtr) area.BaseAddress, (uint) area.RegionSize, out bytesRead);

                if (ArrayContainsPlayerName(buf, localPlayerName, out playersLocalOffset))
                {
                    if (ArrayDoMatchPattern(buf, localPlayerName, playersLocalOffset))
                    {

                        var firstPlayer = GetStartingPlayer(playersLocalOffset, buf);

                        var bs = (int) area.BaseAddress + firstPlayer;

                        playerCount = GetPlayerCount(firstPlayer, buf);

                        if (!t)
                        {




                        }


                        return bs;
                    }
                }
                
            }


            return -1;
        }

        /// <summary>
        /// Gets the address of first player on memory where a random player address is passed through.
        /// </summary>
        /// <param name="randomPlayerAddress">The random player address.</param>
        public int GetStartingPlayer(int randomPlayerAddress, byte[] mem)
        {
            var cnt = randomPlayerAddress;
            var segLength = 20*16;

            while (cnt > 0)
            {
                var nmc = (mem.Skip(cnt).Take(32).ToArray());

                if (!IsPlayerName(nmc))
                    return cnt + segLength;

                cnt -= (20 * 16);
            }

            return cnt + segLength;

        }

        /// <summary>
        /// Gets the count of players.
        /// </summary>
        /// <param name="startingPlayer">The starting player.</param>
        public int GetPlayerCount(int startingPlayer, byte[] mem)
        {
            var cnt = startingPlayer;

            var cnt2 = 0;

            while (cnt < mem.Length - 20*16)
            {
                var nmc = (mem.Skip(cnt).Take(32).ToArray());

                if (!IsPlayerName(nmc))
                    return cnt2 + 1;

                cnt += 20*16;
                cnt2++;
            }

            return cnt2;

        }

        /// <summary>
        /// Determines whether the specified bytes are a possible player name. uses for detecting start and end players.
        /// </summary>
        /// <param name="values">The values.</param>
        private bool IsPlayerName(byte[] values)
        {
            var nm = Encoding.UTF8.GetString(values.TakeWhile(i => i != 0).ToArray());

            if (string.IsNullOrEmpty(nm.Trim()))
                return false;

            foreach (var ch in nm)
            {
                if (char.IsControl(ch))
                    return false;
            }

            return true;
        }

        public static bool ArrayDoMatchPattern(byte[] arr, string playerName, int localOffset)
        {
            var inf = new PlayerData();

            var mem = arr;
            var segLength = 320;

            var j = 0;

            inf.Team = (int) BitConverter.ToUInt32(mem, localOffset + 32);
            inf.Health = (int)BitConverter.ToUInt32(mem, localOffset + 36);

            inf.X = BitConverter.ToSingle(mem, localOffset + 40);
            inf.Y = BitConverter.ToSingle(mem, localOffset + 44);
            inf.Z = BitConverter.ToSingle(mem, localOffset + 48);

            inf.Pitch = BitConverter.ToSingle(mem, localOffset + 52);
            inf.Yaw = BitConverter.ToSingle(mem, localOffset + 56);
            inf.Roll = BitConverter.ToSingle(mem, localOffset + 60);

            if (inf.Team != 1 && inf.Team != 2 && inf.Team != 3)
                return false;

            if (inf.Health < 0 || inf.Health > 100)
                return false;

            if (inf.Pitch < -90 || inf.Pitch > 90)
                return false;

            if (inf.Yaw < -180 || inf.Yaw > 180)
                return false;


            if (Math.Abs(inf.Roll) > 0.01)
                return false;


            for (int i = playerName.Length; i < 32; i++)
            {
                if (arr[localOffset + i] != 0)
                    return false;
            }


            var last = arr[localOffset + 320 - 2];
            var l2 = arr[localOffset + 320 - 1];

            var t3 = (int)BitConverter.ToUInt32(mem, localOffset + 308);
            //if (last != 255 && arr[localOffset + 320 - 1] != 255)
                //return false;

            return true;
        }

        private List<MEMORY_BASIC_INFORMATION> GetMemoryInfo(IntPtr handle)
        {
            var output = new List<MEMORY_BASIC_INFORMATION>();
            var addy = new IntPtr();

            while (true)
            {
                MEMORY_BASIC_INFORMATION memInfo = new MEMORY_BASIC_INFORMATION();

                int memDump = MemoryRead.VirtualQueryEx(handle, addy, out  memInfo, Marshal.SizeOf(memInfo));


                if (memDump == 0)
                {
                    var err = Marshal.GetLastWin32Error();

                    if (err == 0)
                         break;
                    throw new Exception("GetMemoryInfo failed : GetLastError() : " + new Win32Exception(err).Message);
                }

                if ((memInfo.State & 0x1000) != 0 && (memInfo.Protect & 0x100) == 0)
                    output.Add(memInfo);

                addy = new IntPtr(memInfo.BaseAddress.ToInt32() + memInfo.RegionSize);
            }

            return output;
        }

        private ProcessMemoryReaderLib.ProcessMemoryReader currentReader;

        public void RefreshClients(PlayerData[] infos)
        {

            if (currentReader == null)
            {
                currentReader = new ProcessMemoryReaderLib.ProcessMemoryReader();
                currentReader.ReadProcess = Hl2ExeProcess;
            }

            var rdr = currentReader;
            
            int bytes;

            rdr.OpenProcess();

            var mem = rdr.ReadProcessMemory((IntPtr) PlayerListStartAddress, (uint) infos.Length*320, out bytes);
            rdr.CloseHandle();

            RefreshPlayersData(infos, mem);
        }

        public void RefreshPlayersData(PlayerData[] infos, byte[] mem)
        {
            var segLength = 20*16;

            for (int i = 0; i < infos.Length; i++)
            {
                var inf = infos[i];


                if (inf != null)
                {
                    inf.Name = Encoding.UTF8.GetString(mem.Skip(segLength*i).TakeWhile(j => j != 0).ToArray());


                    inf.Team = (int) BitConverter.ToUInt32(mem, segLength*i + 32);
                    inf.Health = (int) BitConverter.ToUInt32(mem, segLength*i + 36);

                    inf.X = BitConverter.ToSingle(mem, segLength*i + 40);
                    inf.Y = BitConverter.ToSingle(mem, segLength*i + 44);
                    inf.Z = BitConverter.ToSingle(mem, segLength*i + 48);

                    inf.Pitch = BitConverter.ToSingle(mem, segLength*i + 52);
                    inf.Yaw = BitConverter.ToSingle(mem, segLength*i + 56);
                    inf.Roll = BitConverter.ToSingle(mem, segLength*i + 60);

                    var t = (int)BitConverter.ToUInt32(mem, segLength * i + 308);
                    if (inf.Name == MainPlayerName)
                    {
                        inf.IsMainPlayer = true;
                        this.WorldData.MainPlayerPitch = inf.Yaw;

                        var loc = new CSPoint() {X = inf.X, Y = inf.Y, Z = inf.Z};

                        this.WorldData.MainPlayerLocation = loc;
                    }
                    else
                    {
                        inf.IsMainPlayer = false;
                    }
                }
            }
        }


      

       
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MEMORY_BASIC_INFORMATION
    {
        public IntPtr BaseAddress;
        public IntPtr AllocationBase;
        public uint AllocationProtect;
        public uint RegionSize;
        public uint State;
        public uint Protect;
        public uint Type;
    }

    public class MemoryRead
    {
        const int PROCESS_WM_READ = 0x0010;

        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

        [DllImport("kernel32.dll")]
        public static extern bool ReadProcessMemory(int hProcess,
          int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);


        
        [DllImport("kernel32.dll")]
        public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);


        public static void Main()
        {
            Process process = Process.GetProcessesByName("notepad")[0];
            IntPtr processHandle = OpenProcess(PROCESS_WM_READ, false, process.Id);

            int bytesRead = 0;
            byte[] buffer = new byte[24]; //'Hello World!' takes 12*2 bytes because of Unicode 


            // 0x0046A3B8 is the address where I found the string, replace it with what you found
            ReadProcessMemory((int)processHandle, 0x0046A3B8, buffer, buffer.Length, ref bytesRead);

            Console.WriteLine(Encoding.Unicode.GetString(buffer) +
               " (" + bytesRead.ToString() + "bytes)");
            Console.ReadLine();
        }
    }
}
